---
title: 'Authentication Context'
tags: 'nextjs,react'
description: 'Great way to structure authentication context in React apps.'
---

This structure of context is adapted from [Kent C Dodds Blog Post](https://kentcdodds.com/blog/how-to-use-react-context-effectively).

## JavaScript Version

```jsx
import axios from 'axios';
import { createContext, useContext, useEffect, useReducer } from 'react';

const StateContext = createContext({
  authenticated: false,
  user: null,
  loading: true,
});

const DispatchContext = createContext(null);

const reducer = (state, { type, payload }) => {
  switch (type) {
    case 'LOGIN':
      return {
        ...state,
        authenticated: true,
        user: payload,
      };
    case 'LOGOUT':
      localStorage.removeItem('token');
      return {
        ...state,
        authenticated: false,
        user: null,
      };
    case 'POPULATE':
      return {
        ...state,
        user: {
          ...state.user,
          ...payload,
        },
      };
    case 'STOP_LOADING':
      return {
        ...state,
        loading: false,
      };
    default:
      throw new Error(`Unknown action type: ${type}`);
  }
};

export const AuthProvider = ({ children }) => {
  const [state, defaultDispatch] = useReducer(reducer, {
    user: null,
    authenticated: false,
    loading: true,
  });

  const dispatch = (type, payload) => defaultDispatch({ type, payload });

  useEffect(() => {
    const loadUser = async () => {
      try {
        const token = localStorage.getItem('token');
        if (token === null || token === undefined) {
          return;
        }
        const res = await axios.get('/profile', {
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${token}`,
          },
        });
        dispatch('LOGIN', res.data.data);
      } catch (err) {
        console.log(err);
        localStorage.removeItem('token');
      } finally {
        dispatch('STOP_LOADING');
      }
    };

    loadUser();
    // eslint-disable-next-line
  }, []);

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
};

export const useAuthState = () => useContext(StateContext);
export const useAuthDispatch = () => useContext(DispatchContext);
```

## Usage

You can wrap your code with `<AuthProvider>` in App.jsx in React or \_app.jsx in Next.js

Then, to use the state and dispatch, we can use these 2 hooks.

```jsx
const { authenticated, user } = useAuthState();
const dispatch = useAuthDispatch();
```

With this context, you can also implement loading, usually in PrivateRoute component

```jsx
// components/PrivateRoute.jsx
import { ImSpinner9 } from 'react-icons/im';
import { Route, Redirect } from 'react-router-dom';
import { useAuthState } from '../contexts/AuthContext';

const PrivateRoute = (props) => {
  const { authenticated, loading } = useAuthState();

  if (loading) {
    return (
      <div className='bg-primary mt-20 flex min-h-screen flex-col items-center justify-center'>
        <ImSpinner9 className='mb-2 animate-spin text-4xl text-yellow-400' />
        <p>Loading...</p>
      </div>
    );
  }

  return authenticated ? <Route {...props} /> : <Redirect to='/login' />;
};
export default PrivateRoute;
```

---

## TypeScript Version

```tsx
import axios from 'axios';
import React, { createContext, useContext, useEffect, useReducer } from 'react';

type User = {
  email: string;
  name: string;
} | null;
type AuthState = {
  authenticated: boolean;
  user: User;
  loading: boolean;
};
type Action =
  | { type: 'LOGIN'; payload: User }
  | { type: 'POPULATE'; payload: User }
  | { type: 'LOGOUT' }
  | { type: 'STOP_LOADING' };
type Dispatch = React.Dispatch<Action>;

const StateContext = createContext<AuthState>({
  authenticated: false,
  user: null,
  loading: true,
});
const DispatchContext = createContext(null);

const reducer = (state: AuthState, action: Action) => {
  switch (action.type) {
    case 'LOGIN':
      return {
        ...state,
        authenticated: true,
        user: action.payload,
      };
    case 'LOGOUT':
      localStorage.removeItem('token');
      return {
        ...state,
        authenticated: false,
        user: null,
      };
    case 'POPULATE':
      return {
        ...state,
        user: {
          ...state.user,
          ...action.payload,
        },
      };
    case 'STOP_LOADING':
      return {
        ...state,
        loading: false,
      };
    default:
      throw new Error('Unknown action type');
  }
};

export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
    authenticated: false,
    loading: true,
  });

  useEffect(() => {
    const loadUser = async () => {
      try {
        const token = localStorage.getItem('token');
        if (token === null || token === undefined) {
          return;
        }
        const res = await axios.get('/profile', {
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${token}`,
          },
        });
        dispatch({ type: 'LOGIN', payload: user });
      } catch (err) {
        // eslint-disable-next-line no-console
        console.log(err);
        localStorage.removeItem('token');
      } finally {
        dispatch({ type: 'STOP_LOADING' });
      }
    };

    loadUser();
    // eslint-disable-next-line
  }, []);

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
};

export const useAuthState = () => useContext(StateContext);
export const useAuthDispatch: () => Dispatch = () =>
  useContext(DispatchContext);
```

## Additional Resources

View more authentication pattern for Next.js to avoid flashing by reading [this blog](/blog/nextjs-redirect-no-flashing).

For Typescript, refer to this [demo site](https://ts-auth.theodorusclarence.com).
